<template>
  <canvas
    :style="{ width: cv_width + 'px', height: cv_height + 'px' }"
    :canvas-id="posterId"
    :id="posterId"
    class="tui-poster__cv"
  ></canvas>
</template>

<script>
// #ifdef MP-WEIXIN
const posterId = `poster_${Math.ceil(Math.random() * 10e5).toString(36)}`
// #endif
export default {
  name: 'tui-poster',
  emits: ['ready'],
  props: {
    //海报宽度，单位rpx
    width: {
      type: [Number, String],
      default: 800
    },
    //海报高度，单位rpx
    height: {
      type: [Number, String],
      default: 1200
    },
    //像素比
    pixel: {
      type: [Number, String],
      default: 3
    }
  },
  watch: {
    width(val) {
      this.cv_width = this.getPX(this.width)
    },
    height(val) {
      this.cv_height = this.getPX(this.height)
    }
  },
  data() {
    // #ifndef MP-WEIXIN
    const posterId = `poster_${Math.ceil(Math.random() * 10e5).toString(36)}`
    // #endif
    return {
      posterId: posterId,
      cv_width: 400,
      cv_height: 600
    }
  },
  created() {
    this.cv_width = this.getPX(this.width)
    this.cv_height = this.getPX(this.height)
    this.ctx = null
  },
  mounted() {
    setTimeout(() => {
      this.ctx = uni.createCanvasContext(this.posterId, this)
      this.$emit('ready', {})
    }, 50)
  },
  methods: {
    toast(msg) {
      uni.showToast({
        title: msg,
        icon: 'none'
      })
    },
    getPX(val) {
      return uni.upx2px(Number(val) * Number(this.pixel))
    },
    getTextWidth(context, text, fontSize) {
      let width = 0
      // #ifndef MP-ALIPAY || MP-BAIDU
      width = context.measureText(text).width
      // #endif
      // #ifdef MP-ALIPAY || MP-BAIDU
      let sum = 0
      for (let i = 0, len = text.length; i < len; i++) {
        if (text.charCodeAt(i) >= 0 && text.charCodeAt(i) <= 255) sum = sum + 1
        else sum = sum + 2
      }
      width = (sum / 2) * this.getPX(fontSize)
      // #endif
      return width
    },
    getWrapText(text, fontSize, textWidth, width, ctx, rows = 2) {
      let textArr = []
      if (textWidth > width) {
        let fillText = ''
        let lines = 1
        let arr = text.split('')
        for (let i = 0, len = arr.length; i < len; i++) {
          fillText = fillText + arr[i]
          if (this.getTextWidth(ctx, fillText, fontSize) >= width) {
            if (lines === rows && rows !== -1) {
              if (i !== arr.length - 1) {
                fillText = fillText.substring(0, fillText.length - 1) + '...'
              }
              textArr.push(fillText)
              break
            }
            textArr.push(fillText)
            fillText = ''
            lines++
          } else if (i === arr.length - 1) {
            textArr.push(fillText)
          }
        }
      } else {
        textArr.push(text)
      }
      return textArr
    },
    startDrawText(ctx, param) {
      let styles = param.style || {}
      let {
        left,
        top,
        fontSize,
        color,
        baseLine = 'normal',
        textAlign = 'left',
        frontSize,
        spacing,
        opacity = 1,
        lineThrough = false,
        width = 600,
        rows = 1,
        lineHeight = 0,
        fontWeight = 'normal',
        fontStyle = 'normal',
        fontFamily = 'sans-serif'
      } = styles
      ctx.save()
      ctx.beginPath()
      ctx.font =
        fontStyle +
        ' ' +
        fontWeight +
        ' ' +
        this.getPX(fontSize) +
        'px ' +
        fontFamily
      ctx.setGlobalAlpha(opacity)
      // ctx.setFontSize(this.getPX(fontSize));
      ctx.setFillStyle(color)
      ctx.setTextBaseline(baseLine)
      ctx.setTextAlign(textAlign)
      let textWidth = this.getTextWidth(ctx, param.text, fontSize)
      width = this.getPX(width)
      let textArr = this.getWrapText(
        param.text,
        fontSize,
        textWidth,
        width,
        ctx,
        rows
      )
      if (param.frontText) {
        ctx.setFontSize(this.getPX(frontSize))
        left =
          this.getTextWidth(ctx, param.frontText, frontSize) +
          this.getPX(left + spacing)
        ctx.setFontSize(this.getPX(fontSize))
      } else {
        left = this.getPX(left)
      }
      textArr.forEach((item, index) => {
        ctx.fillText(
          item,
          left,
          this.getPX(top + (lineHeight || fontSize) * index)
        )
      })
      ctx.restore()
      if (lineThrough) {
        let lineY = top
        switch (baseLine) {
          case 'top':
            lineY += fontSize / 2 + 4
            break
          case 'middle':
            break
          case 'bottom':
            lineY -= fontSize / 2 + 4
            break
          default:
            // #ifdef MP-WEIXIN
            lineY -= fontSize / 2 - 3
            // #endif
            // #ifndef MP-WEIXIN
            lineY -= fontSize / 2 - 4
            // #endif
            break
        }
        ctx.save()
        ctx.moveTo(left, this.getPX(lineY))
        ctx.lineTo(left + textWidth + 2, this.getPX(lineY))
        ctx.setStrokeStyle(color)
        ctx.stroke()
        ctx.restore()
      }
    },
    drawRadiusRect(ctx, styles) {
      let { left, top, width, height, borderRadius } = styles
      let r = this.getPX(borderRadius / 2)

      left = this.getPX(left)
      top = this.getPX(top)
      width = this.getPX(width)
      height = this.getPX(height)

      ctx.beginPath()
      ctx.arc(left + r, top + r, r, Math.PI, Math.PI * 1.5)
      ctx.moveTo(left + r, top)
      ctx.lineTo(left + width - r, top)
      ctx.lineTo(left + width, top + r)

      ctx.arc(left + width - r, top + r, r, Math.PI * 1.5, Math.PI * 2)
      ctx.lineTo(left + width, top + height - r)
      ctx.lineTo(left + width - r, top + height)

      ctx.arc(left + width - r, top + height - r, r, 0, Math.PI * 0.5)
      ctx.lineTo(left + r, top + height)
      ctx.lineTo(left, top + height - r)

      ctx.arc(left + r, top + height - r, r, Math.PI * 0.5, Math.PI)
      ctx.lineTo(left, top + r)
      ctx.lineTo(left + r, top)
    },
    startDrawImage(ctx, param) {
      let styles = param.style || {}
      let {
        left,
        top,
        width,
        height,
        borderRadius = 0,
        borderWidth = 0,
        borderColor
      } = styles
      ctx.save()
      if (borderRadius > 0) {
        this.drawRadiusRect(ctx, styles)
        ctx.strokeStyle = 'rgba(0,0,0,0)'
        // #ifndef MP-BAIDU || MP-TOUTIAO
        ctx.stroke()
        // #endif
        ctx.clip()
      }
      ctx.drawImage(
        param.src,
        this.getPX(left),
        this.getPX(top),
        this.getPX(width),
        this.getPX(height)
      )
      if (borderWidth && borderWidth > 0) {
        ctx.setStrokeStyle(borderColor)
        ctx.setLineWidth(this.getPX(borderWidth))
        ctx.stroke()
      }
      ctx.restore()
    },
    startDrawRect(ctx, param) {
      let styles = param.style || {}
      let {
        width,
        height,
        left,
        top,
        borderWidth,
        backgroundColor,
        gradientColor,
        gradientType = 1,
        borderColor,
        borderRadius = 0,
        opacity = 1,
        shadow
      } = styles
      if (backgroundColor) {
        ctx.save()
        ctx.setGlobalAlpha(opacity)
        if (gradientColor) {
          let grd = null
          if (gradientType == 1) {
            grd = ctx.createLinearGradient(
              0,
              0,
              this.getPX(width),
              this.getPX(height)
            )
          } else {
            grd = ctx.createLinearGradient(
              0,
              this.getPX(width),
              this.getPX(height),
              0
            )
          }
          grd.addColorStop(0, backgroundColor)
          grd.addColorStop(1, gradientColor)
          ctx.setFillStyle(grd)
        } else {
          ctx.setFillStyle(backgroundColor)
        }

        if (shadow) {
          const { offsetX, offsetY, blur, color } = shadow
          ctx.shadowOffsetX = this.getPX(offsetX)
          ctx.shadowOffsetY = this.getPX(offsetY)
          ctx.shadowBlur = blur
          ctx.shadowColor = color
        }

        if (borderRadius > 0) {
          this.drawRadiusRect(ctx, styles)
          ctx.fill()
        } else {
          ctx.fillRect(
            this.getPX(left),
            this.getPX(top),
            this.getPX(width),
            this.getPX(height)
          )
        }
        ctx.restore()
      }
      if (borderWidth) {
        ctx.save()
        ctx.setGlobalAlpha(opacity)
        ctx.setStrokeStyle(borderColor)
        ctx.setLineWidth(this.getPX(borderWidth))
        if (borderRadius > 0) {
          this.drawRadiusRect(ctx, styles)
          ctx.stroke()
        } else {
          ctx.strokeRect(
            this.getPX(left),
            this.getPX(top),
            this.getPX(width),
            this.getPX(height)
          )
        }
        ctx.restore()
      }
    },
    startDrawLine(ctx, param) {
      let styles = param.style
      let { left, top, endLeft, endTop, color, width = 1 } = styles
      ctx.save()
      ctx.beginPath()
      ctx.setStrokeStyle(color)
      ctx.setLineWidth(this.getPX(width))
      ctx.moveTo(this.getPX(left), this.getPX(top))
      ctx.lineTo(this.getPX(endLeft), this.getPX(endTop))
      ctx.stroke()
      ctx.closePath()
      ctx.restore()
    },
    judgeIosPermissionPhotoLibrary() {
      // #ifdef APP-PLUS
      var result = 0
      var PHPhotoLibrary = plus.ios.import('PHPhotoLibrary')
      var authStatus = PHPhotoLibrary.authorizationStatus()
      if (authStatus === 0) {
        result = -1
      } else if (authStatus == 3) {
        result = 1
        console.log('相册权限已经开启')
      } else {
        result = 0
        console.log('相册权限没有开启')
      }
      plus.ios.deleteObject(PHPhotoLibrary)
      return result
      // #endif
    },
    requestAndroidPermission(permissionID) {
      // #ifdef APP-PLUS
      return new Promise((resolve, reject) => {
        plus.android.requestPermissions(
          [permissionID],
          function (resultObj) {
            var result = 0
            for (var i = 0; i < resultObj.granted.length; i++) {
              var grantedPermission = resultObj.granted[i]
              result = 1
            }
            for (var i = 0; i < resultObj.deniedPresent.length; i++) {
              var deniedPresentPermission = resultObj.deniedPresent[i]
              result = 0
            }
            for (var i = 0; i < resultObj.deniedAlways.length; i++) {
              var deniedAlwaysPermission = resultObj.deniedAlways[i]
              result = -1
            }
            resolve(result)
          },
          function (error) {
            resolve({
              code: error.code,
              message: error.message
            })
          }
        )
      })
      // #endif
    },
    gotoAppPermissionSetting(isAndroid) {
      // #ifdef APP-PLUS
      if (!isAndroid) {
        var UIApplication = plus.ios.import('UIApplication')
        var application2 = UIApplication.sharedApplication()
        var NSURL2 = plus.ios.import('NSURL')
        var setting2 = NSURL2.URLWithString('app-settings:')
        application2.openURL(setting2)

        plus.ios.deleteObject(setting2)
        plus.ios.deleteObject(NSURL2)
        plus.ios.deleteObject(application2)
      } else {
        var Intent = plus.android.importClass('android.content.Intent')
        var Settings = plus.android.importClass('android.provider.Settings')
        var Uri = plus.android.importClass('android.net.Uri')
        var mainActivity = plus.android.runtimeMainActivity()
        var intent = new Intent()
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
        var uri = Uri.fromParts('package', mainActivity.getPackageName(), null)
        intent.setData(uri)
        mainActivity.startActivity(intent)
      }
      // #endif
    },
    judgePermissionPhotoLibrary: async function (callback) {
      // #ifndef APP-PLUS || MP-WEIXIN || MP-QQ
      callback && callback(true)
      // #endif

      // #ifdef APP-PLUS
      const res = uni.getSystemInfoSync()
      let result
      let isAndroid = res.platform.toLocaleLowerCase() == 'android'
      if (isAndroid) {
        result = await this.requestAndroidPermission(
          'android.permission.WRITE_EXTERNAL_STORAGE'
        )
      } else {
        result = this.judgeIosPermissionPhotoLibrary()
      }
      if (result == 1) {
        callback && callback(true)
      } else {
        if (!(!isAndroid && result == -1)) {
          uni.showModal({
            title: '提示',
            content: '您还没有开启相册权限，是否立即开启？',
            showCancel: true,
            success: (res) => {
              if (res.confirm) {
                this.gotoAppPermissionSetting(isAndroid)
              }
            }
          })
        } else {
          callback && callback(true)
        }
      }
      // #endif

      // #ifdef MP-WEIXIN || MP-QQ
      uni.authorize({
        scope: 'scope.writePhotosAlbum',
        success() {
          callback && callback(true)
        },
        fail() {
          uni.showModal({
            title: '提示',
            content: '您还没有开启相册权限，是否立即开启？',
            showCancel: true,
            success: (res) => {
              if (res.confirm) {
                wx.openSetting({
                  success(res) {}
                })
              }
            }
          })
        }
      })
      // #endif
    },
    imgDownload(url) {
      return new Promise((resolve, reject) => {
        uni.downloadFile({
          url: url,
          success: (res) => {
            resolve(res.tempFilePath)
          },
          fail: (err) => {
            reject(false)
          }
        })
      })
    },
    base64ToImg(base64) {
      const uniqueId = `poster_${Math.ceil(Math.random() * 10e5).toString(36)}`
      return new Promise((resolve, reject) => {
        // #ifdef MP-WEIXIN
        const [, format, bodyData] =
          /data:image\/(\w+);base64,(.*)/.exec(base64) || []
        let arrayBuffer = wx.base64ToArrayBuffer(bodyData)
        const filePath = `${wx.env.USER_DATA_PATH}/${uniqueId}.${format}`
        wx.getFileSystemManager().writeFile({
          filePath,
          data: arrayBuffer,
          encoding: 'binary',
          success() {
            resolve(filePath)
          },
          fail() {
            reject(false)
          }
        })
        // #endif
        // #ifdef APP-PLUS
        let bitmap = new plus.nativeObj.Bitmap(uniqueId)
        bitmap.loadBase64Data(
          base64,
          function () {
            bitmap.save(
              `_doc/${uniqueId}.png`,
              {},
              function (e) {
                let target = e.target
                resolve(target)
              },
              function (e) {
                reject(false)
              }
            )
          },
          function () {
            reject(false)
          }
        )
        // #endif

        // #ifdef H5
        resolve(base64)
        // #endif

        // #ifndef MP-WEIXIN || APP-PLUS || H5
        reject(false)
        // #endif
      })
    },
    startDraw(data, callback) {
      let ctx = this.ctx
      if (ctx) {
        ctx.clearRect(0, 0, this.cv_width, this.cv_height)
        data.forEach((item) => {
          if (item.type === 'image') {
            this.startDrawImage(ctx, item)
          } else if (item.type === 'text') {
            this.startDrawText(ctx, item)
          } else if (item.type === 'rect') {
            this.startDrawRect(ctx, item)
          } else if (item.type === 'line') {
            this.startDrawLine(ctx, item)
          }
        })
        const platform = uni.getSystemInfoSync().platform
        let time = 80
        if (platform === 'android') {
          time = 300
        }
        setTimeout(() => {
          ctx.draw(false, () => {
            setTimeout(() => {
              // #ifdef MP-ALIPAY
              ctx.toTempFilePath({
                success: (res) => {
                  callback && callback(res.apFilePath)
                },
                fail: (err) => {
                  callback && callback(false)
                }
              })
              // #endif
              // #ifndef MP-ALIPAY
              uni.canvasToTempFilePath(
                {
                  x: 0,
                  y: 0,
                  canvasId: this.posterId,
                  fileType: 'png',
                  quality: 1,
                  success: function (res) {
                    callback && callback(res.tempFilePath)
                  },
                  fail() {
                    callback && callback(false)
                  }
                },
                this
              )
              // #endif
            }, time)
          })
        }, 50)
      } else {
        callback && callback(false)
      }
    },
    draw(data, callback) {
      // text（文本）、image（图片）、rect（矩形），line（线条）
      // {
      // 	type:'image',
      //  src:'',
      // 	style:{

      // 	}
      // }
      if (!data || data.length === 0) return
      let func = [],
        idxes = []
      data.forEach((item, index) => {
        if (item.type === 'image') {
          //图片类型：1-本地图片（需要平台支持）；2-网络图片； 3- base64 图片（仅App，微信小程序，H5支持）
          if (item.imgType == 2) {
            func.push(this.imgDownload(item.src))
            idxes.push(index)
          }
          // #ifdef APP-PLUS || H5 || MP-WEIXIN
          if (item.imgType == 3) {
            func.push(this.base64ToImg(item.src))
            idxes.push(index)
          }
          // #endif
        }
      })
      if (func.length > 0) {
        Promise.all(func)
          .then((res) => {
            res.forEach((imgRes, idx) => {
              let item = data[idxes[idx]]
              item.src = imgRes
            })
            this.startDraw(data, callback)
          })
          .catch((err) => {
            console.log(err)
            this.toast('图片处理失败!')
          })
      } else {
        this.startDraw(data, callback)
      }
    },
    save(file) {
      // #ifdef H5
      //H5无法直接保存到相册，预览后长按保存
      uni.previewImage({
        urls: [file]
      })
      // #endif
      // #ifndef H5
      this.judgePermissionPhotoLibrary((res) => {
        if (res) {
          uni.saveImageToPhotosAlbum({
            filePath: file,
            success: (res) => {
              this.toast('图片已保存到相册')
            },
            fail: (res) => {
              this.toast('图片保存失败')
            }
          })
        }
      })
      // #endif
    }
  }
}
</script>

<style scoped>
.tui-poster__cv {
  position: fixed;
  left: -9999px;
  bottom: 0;
}
</style>
